Skip to content

feat(mcp): serve HTML landing page on browser GET to MCP endpoint#1072

Merged
Aaron ("AJ") Steers (aaronsteers) merged 9 commits into
mainfrom
devin/1783972828-mcp-browser-landing-page
Jul 14, 2026
Merged

feat(mcp): serve HTML landing page on browser GET to MCP endpoint#1072
Aaron ("AJ") Steers (aaronsteers) merged 9 commits into
mainfrom
devin/1783972828-mcp-browser-landing-page

Conversation

@aaronsteers

@aaronsteers Aaron ("AJ") Steers (aaronsteers) commented Jul 13, 2026

Copy link
Copy Markdown
Member

Summary

A browser GET to the Cloud MCP endpoint (https://mcp.internal.airbyte.ai/cloud-mcp) previously returned a bare 405 Method Not Allowed. This serves a human-readable landing page instead ("this is an MCP endpoint, add it to your client" + the endpoint URL + a docs link), without touching MCP POST/DELETE or OIDC.

The landing-page HTML + route wiring live upstream in fastmcp-extensions; http_main.py just calls the shared helper:

from fastmcp_extensions import register_landing_page

# in main(), before app.run(...)
register_landing_page(
    app,
    path=mcp_path,                 # "/" behind the path-stripping LB, "/mcp" locally
    title=MCP_LANDING_TITLE,
    endpoint_url=_get_server_url(),
    docs_url=MCP_LANDING_DOCS_URL,
)

This deletes the local _render_mcp_landing_html / mcp_landing_page copy (~60 lines) and drops the now-unused html/HTMLResponse/Request imports. In stateless streamable-HTTP mode FastMCP binds only POST/DELETE to mcp_path, so the same-path GET route serves browsers without intercepting MCP traffic.

register_landing_page shipped in fastmcp-extensions (airbytehq/fastmcp-extensions#65, released as 0.9.0). The dependency is pinned to fastmcp-extensions>=0.9.0,<1.0.0 and uv.lock resolves the stable 0.9.0.

Test plan

  • Verified locally: import airbyte.mcp.http_main succeeds and exposes main; ruff check/ruff format --check clean.
  • After merge + a new PyAirbyte release + cloud-mcp redeploy: curl -i https://mcp.internal.airbyte.ai/cloud-mcp200 text/html; unauthenticated POST401.

Link to Devin session: https://app.devin.ai/sessions/5bfc7d5ac9544cf1be05a9b1631cc890
Requested by: Aaron ("AJ") Steers (@aaronsteers)

Important

Auto-merge enabled.

This PR is set to merge automatically when all requirements are met.

Note

Auto-merge may have been disabled. Please check the PR status to confirm.

@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@github-actions

Copy link
Copy Markdown

👋 Greetings, Airbyte Team Member!

Here are some helpful tips and reminders for your convenience.

💡 Show Tips and Tricks

Testing This PyAirbyte Version

You can test this version of PyAirbyte using the following:

# Run PyAirbyte CLI from this branch:
uvx --from 'git+https://github.com/airbytehq/PyAirbyte.git@devin/1783972828-mcp-browser-landing-page' pyairbyte --help

# Install PyAirbyte from this branch for development:
pip install 'git+https://github.com/airbytehq/PyAirbyte.git@devin/1783972828-mcp-browser-landing-page'

PR Slash Commands

Airbyte Maintainers can execute the following slash commands on your PR:

  • /fix-pr - Fixes most formatting and linting issues
  • /uv-lock - Updates uv.lock file
  • /test-pr - Runs tests with the updated PyAirbyte
  • /prerelease - Builds and publishes a prerelease version to PyPI
📚 Show Repo Guidance

Helpful Resources

Community Support

Questions? Join the #pyairbyte channel in our Slack workspace.

📝 Edit this welcome message.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a browser-friendly landing page to the computed MCP HTTP mount path, resolves the server URL with a localhost fallback, and raises the minimum fastmcp-extensions version while preserving existing MCP HTTP transport setup.

Changes

MCP landing page

Layer / File(s) Summary
Landing page configuration
airbyte/mcp/http_main.py, pyproject.toml
Adds landing-page title and documentation constants, resolves MCP_SERVER_URL with a localhost fallback, imports register_landing_page, and requires fastmcp-extensions 0.9.0 or newer.
GET route wiring
airbyte/mcp/http_main.py
Computes mcp_path from the resolved server URL and registers the landing page with the endpoint and documentation URL while retaining existing POST and DELETE transport setup.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MCPServer
  participant http_main.py
  participant fastmcp_extensions
  MCPServer->>http_main.py: Start main()
  http_main.py->>http_main.py: Resolve server URL and MCP path
  http_main.py->>fastmcp_extensions: Register GET landing page
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding an HTML landing page for browser GET requests to the MCP endpoint.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch devin/1783972828-mcp-browser-landing-page

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
airbyte/mcp/http_main.py (1)

101-107: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the MCP_SERVER_URL lookup into a shared helper?

The os.getenv(MCP_SERVER_URL_ENV, ...) block at lines 103-106 duplicates the same logic in main() at lines 117-120. If the default URL format ever changes, both spots need updating. Would it make sense to extract a small helper like _get_server_url() and use it in both places? wdyt?

♻️ Proposed refactor
+def _get_server_url() -> str:
+    return os.getenv(
+        MCP_SERVER_URL_ENV,
+        f"http://localhost:{DEFAULT_HTTP_PORT}",
+    )
+
 async def mcp_landing_page(request: Request) -> HTMLResponse:  # noqa: ARG001, RUF029
     """Serve a human-friendly landing page for browser `GET`s to the MCP endpoint."""
-    server_url = os.getenv(
-        MCP_SERVER_URL_ENV,
-        f"http://localhost:{DEFAULT_HTTP_PORT}",
-    )
+    server_url = _get_server_url()
     return HTMLResponse(_render_mcp_landing_html(server_url, MCP_LANDING_DOCS_URL))

And in main():

 def main() -> None:
     """Start the Airbyte MCP server with HTTP transport."""
     logging.basicConfig(level=logging.INFO)

-    server_url = os.getenv(
-        MCP_SERVER_URL_ENV,
-        f"http://localhost:{DEFAULT_HTTP_PORT}",
-    )
+    server_url = _get_server_url()
     mcp_path = "/" if urlparse(server_url).path.strip("/") else "/mcp"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@airbyte/mcp/http_main.py` around lines 101 - 107, Extract the duplicated
MCP_SERVER_URL_ENV lookup and default URL construction into a shared helper such
as _get_server_url(), then replace the inline logic in both mcp_landing_page and
main with that helper. Preserve the existing environment-variable and
default-value behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@airbyte/mcp/http_main.py`:
- Around line 101-107: Extract the duplicated MCP_SERVER_URL_ENV lookup and
default URL construction into a shared helper such as _get_server_url(), then
replace the inline logic in both mcp_landing_page and main with that helper.
Preserve the existing environment-variable and default-value behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 478eac4f-c128-4e9b-ba43-6c3d8228af9f

📥 Commits

Reviewing files that changed from the base of the PR and between 15ca9ab and fd79b33.

📒 Files selected for processing (1)
  • airbyte/mcp/http_main.py

Co-Authored-By: AJ Steers <aj@airbyte.io>
devin-ai-integration[bot]

This comment was marked as resolved.

@github-code-quality

github-code-quality Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: Python

Python / code-coverage/pytest-fast

The overall coverage in the branch is 67%. The coverage in the branch is 65%.

Show a code coverage summary of the most impacted files.
File d9f652f fe01d45 +/-
airbyte/_util/api_util.py 36% 37% +1%
airbyte/registry.py 70% 72% +2%
airbyte/mcp/server.py 69% 78% +9%
airbyte/mcp/int...c_history_ui.py 0% 36% +36%
airbyte/mcp/int...hared_models.py 0% 81% +81%
airbyte/cloud/models.py 0% 91% +91%
airbyte/mcp/int..._registry_ui.py 0% 92% +92%
airbyte/mcp/int...nc_status_ui.py 0% 97% +97%
airbyte/mcp/int...ive/__init__.py 0% 100% +100%
airbyte/mcp/int...tive/_prefab.py 0% 100% +100%

Python / code-coverage/pytest-no-creds

The overall coverage in the branch is 67%. The coverage in the branch is 65%.

Show a code coverage summary of the most impacted files.
File d9f652f fe01d45 +/-
airbyte/_util/api_util.py 36% 37% +1%
airbyte/registry.py 70% 72% +2%
airbyte/mcp/server.py 69% 78% +9%
airbyte/mcp/int...c_history_ui.py 0% 36% +36%
airbyte/mcp/int...hared_models.py 0% 81% +81%
airbyte/cloud/models.py 0% 91% +91%
airbyte/mcp/int..._registry_ui.py 0% 92% +92%
airbyte/mcp/int...nc_status_ui.py 0% 97% +97%
airbyte/mcp/int...ive/__init__.py 0% 100% +100%
airbyte/mcp/int...tive/_prefab.py 0% 100% +100%

Python / code-coverage/pytest

The overall coverage in the branch is 72%. The coverage in the branch is 71%.

Show a code coverage summary of the most impacted files.
File d9f652f fe01d45 +/-
airbyte/_util/api_util.py 72% 72% 0%
airbyte/registry.py 70% 72% +2%
airbyte/mcp/server.py 69% 78% +9%
airbyte/mcp/int...c_history_ui.py 0% 36% +36%
airbyte/mcp/int...hared_models.py 0% 81% +81%
airbyte/mcp/int..._registry_ui.py 0% 92% +92%
airbyte/cloud/models.py 0% 93% +93%
airbyte/mcp/int...nc_status_ui.py 0% 97% +97%
airbyte/mcp/int...ive/__init__.py 0% 100% +100%
airbyte/mcp/int...tive/_prefab.py 0% 100% +100%

Updated July 14, 2026 07:12 UTC
Code Coverage is in Public Preview. Learn more and provide us with your feedback.

coderabbitai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 3 commits July 14, 2026 01:26
Co-Authored-By: AJ Steers <aj@airbyte.io>
…_landing_page

Co-Authored-By: AJ Steers <aj@airbyte.io>
…alidation

Co-Authored-By: AJ Steers <aj@airbyte.io>
Co-Authored-By: AJ Steers <aj@airbyte.io>
@aaronsteers Aaron ("AJ") Steers (aaronsteers) marked this pull request as ready for review July 14, 2026 05:34
Copilot AI review requested due to automatic review settings July 14, 2026 05:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the developer/operator experience for the Airbyte Cloud MCP endpoint by serving a human-readable HTML landing page when the MCP endpoint is accessed via a browser GET, while keeping MCP POST/DELETE behavior (and OIDC) unchanged.

Changes:

  • Bumps fastmcp-extensions to >=0.9.0,<1.0.0 to use the shared register_landing_page helper.
  • Wires register_landing_page() into airbyte/mcp/http_main.py and computes the correct advertised MCP endpoint URL based on mount path.
  • Updates uv.lock to resolve fastmcp-extensions==0.9.0 and its dependency set.

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated no comments.

File Description
uv.lock Updates the resolved fastmcp-extensions version to 0.9.0 and reflects its dependency metadata.
pyproject.toml Raises the fastmcp-extensions minimum version to 0.9.0 to ensure register_landing_page is available.
airbyte/mcp/http_main.py Registers a GET-only landing page at the MCP mount path and centralizes server URL retrieval for endpoint computation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review

Co-Authored-By: AJ Steers <aj@airbyte.io>
@aaronsteers Aaron ("AJ") Steers (aaronsteers) merged commit 5ba44a2 into main Jul 14, 2026
20 of 22 checks passed
@aaronsteers Aaron ("AJ") Steers (aaronsteers) deleted the devin/1783972828-mcp-browser-landing-page branch July 14, 2026 07:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants